/**
 * Required Features:
 * 
 * 1. Code auto-complete --> done
 * 2. Indentation functionality --> (not implemented)
 * 3. Auto-complete floating menu --> in progress
 * 4. Syntax validation --> in progress
 * 5. RUN script
 * 6. save file
 * 7. Errors highlighting and reporting
 * 8. Check TODOs
 * 
 */

if (!dojo._hasResource['dojoide.workspace.CodeEditor']) {
    dojo._hasResource['dojoide.workspace.CodeEditor'] = true;
    dojo.provide('dojoide.workspace.CodeEditor');
    
	dojo.require('dijit.Editor');
	dojo.require('dijit._editor.range');
	dojo.require('dijit._editor.selection');

	dojo.require('dojoide.core._utils');
	dojo.require('dojoide.core._autocomplete');
	dojo.require('dojoide.workspace.OutputPane');
	dojo.require('dojoide.workspace.AutoCompletePane');

	//Dojo IDE CodeEditor
    dojo.declare("dojoide.workspace.CodeEditor", [dijit.Editor], {
    	height: '100%',
    	width: '100%',

    	plugins: [],
    	
        msgPane: null,
        
        autoCompPane: null,
        
        _locTemplate: null,
        
        /*
         * overriding functions - Begin
         */
        postCreate: function(){
            this.inherited('postCreate', arguments);
            
            //define CaretPos classifier
            dojoide.workspace.CodeEditor.CaretPos = function(row, col){
            	this.row = row==null?-1:parseInt(row);
            	this.col = col==null?-1:parseInt(col);
				this.toString = function(){
					return this.row + ':' + this.col;
				}
            }
            
            //define ValidationError classifier
            dojoide.workspace.CodeEditor.ErrorWrapper = function(errorObj){
            	this.errorObj = errorObj;
				this.toString = function(){
					var msg = this.errorObj.reason?this.errorObj.reason:this.errorObj;
					if (this.errorObj.line != null && this.errorObj.character != null){
						msg += ' [ ' + this.errorObj.line + ' : ' + this.errorObj.character + ' ]';
					}
					return msg;
				}
            }
            
            
            if (!dojo.isIE && !dojo.isFF){//IE and FF only supported for now
            	console.log('DojoIDE does not support this browser');
            }
        },
        
        //function override
        onLoad: function(/*Event*/e){
    		this.inherited('onLoad', arguments);
            
            this.editNode.style.height = this.height;
            this.editNode.style.width = this.width;

            //this.setValue('<div _loc="true" style="none">&nbsp;</div>');
            this.setValue('<div _loc="true" style="border: 1px solid blue">&nbsp;</div>');
            this.focus();
            this.selectRange(this.editNode.childNodes[0], 1);

			//initialize _locTemplate
			this._locTemplate = this.document.createElement('div');//create div element
			var locAttr = this.document.createAttribute('_loc');//set _loc attribute
			locAttr.value = 'true';//set _loc attribute value
			this._locTemplate.attributes.setNamedItem(locAttr);
			//this._locTemplate.style.border = 'none';
			this._locTemplate.style.border = '1px solid blue';
			this._locTemplate.innerHTML = '&nbsp;';
        },

        onBlur: function(){
            this.inherited('onBlur', arguments);
            this._blurRange = this.getSelectedRange();
        },
        
        onFocus: function(){
            this.inherited('onFocus', arguments);
            if (this._blurRange == null){
            	this.placeCursorAtEnd();
            } else {
	        	this.setSelectedRange(this._blurRange);
            }
        },
        
		setupDefaultShortcuts: function(){
			// summary: add some default key handlers
			// description:
			//		adding some code editor shortcuts 
			var exec = function(cmd, arg){
				return arguments.length == 1 ? function(){ this.execCommand(cmd); } :
					function(){ this.execCommand(cmd, arg); };
			};
	
			var ctrlKeyHandlers = {
				a: exec("selectall"),
				y: exec("redo"),
				z: exec("undo"),
				
				c: function(){//copy
					alert('not yet implemented');
				},
				x: function(){//cut
					alert('not yet implemented');
				},
				v: function(){//paste
					alert('not yet implemented');
				},
				s: function(){//save
					alert('not yet implemented');
				},

				b: function(){//bold - disabled
					//override to disable
				},
				i: function(){//italics - disabled
					//override to disable
				},
				u: function(){//underline - disabled
					//override to disable
				},

				' ': function(){//show auto complete list
                    this.showAutoCompleteList();
				}
			};
	
			for(var key in ctrlKeyHandlers){
				this.addKeyHandler(key, true, false, ctrlKeyHandlers[key]);
			}
		},

        onKeyDown: function(/*Event*/e){
    		this.inherited('onKeyDown', arguments);

        	//catch keyCodes
        	if(!e.keyCode){//no keyCode found
        		return;
        	}
        	
			switch(e.keyCode){
				case dojo.keys.LEFT_ARROW:
					this._updateAutoCompleteList = true;
					break;
				case dojo.keys.RIGHT_ARROW:
					this._updateAutoCompleteList = true;
					break;
				case dojo.keys.UP_ARROW:
					if(this.autoCompPane.isVisible()){
						dojo.stopEvent(e);
						this.autoCompPane.moveSelection(-1);
					}
					break;
				case dojo.keys.DOWN_ARROW:
					if(this.autoCompPane.isVisible()){
						dojo.stopEvent(e);
						this.autoCompPane.moveSelection(1);
					}
					break;
				case dojo.keys.PAGE_UP:
					if(this.autoCompPane.isVisible()){
						dojo.stopEvent(e);
						this.autoCompPane.moveSelection(-10);
					}
					break;
				case dojo.keys.PAGE_DOWN:
					if(this.autoCompPane.isVisible()){
						dojo.stopEvent(e);
						this.autoCompPane.moveSelection(10);
					}
					break;
				case dojo.keys.HOME:
					if(this.autoCompPane.isVisible()){
						dojo.stopEvent(e);
						this.autoCompPane.moveSelection(null, 0);
					}
					break;
				case dojo.keys.END:
					if(this.autoCompPane.isVisible()){
						dojo.stopEvent(e);
						this.autoCompPane.moveSelection(null, this.autoCompPane.listedObjects.length - 1);
					}
					break;
				case dojo.keys.TAB:
					this.insertHtml('&nbsp;&nbsp;&nbsp;&nbsp;');
					break;
				case dojo.keys.BACKSPACE:
					var range = this.getSelectedRange();
					if (range.collapsed && range.endOffset != 0){//cursor is not at the beginning of the line
						this._updateAutoCompleteList = true;
					}
					break;
				case dojo.keys.ENTER:
					if(this.autoCompPane.isVisible()){
						dojo.stopEvent(e);
						this.insertSuggestion();
	                    this.hideAutoCompleteList();
					} else {
						dojo.stopEvent(e);
						this.insertNewLine();
					}
					break;
				case dojo.keys.ESCAPE:
					if(this.autoCompPane.isVisible()){
						dojo.stopEvent(e);
	                    this.hideAutoCompleteList();
					}
					break;
				case dojo.keys.F4:
					dojo.stopEvent(e);
					this.output('',true);
					break;
				case dojo.keys.F8:
					//Validate script
					dojo.stopEvent(e);
					this.validateScript();
					break;
				case dojo.keys.F9:
					//RUN
					dojo.stopEvent(e);
					this.runScript();
					break;
			}
        },

        onKeyPressed: function(/*Event*/e){
            this.inherited('onKeyPressed', arguments);
			
			if((this._updateAutoCompleteList && this._updateAutoCompleteList == true) && this.autoCompPane.isVisible()){
				this.showAutoCompleteList();
				this._updateAutoCompleteList = false;
			}
			
        	//catch keyChars
        	if(!e.keyChar){//no keyChar found
        		return;
        	}

			switch(e.keyChar){
				case '.'://dot
					this.showAutoCompleteList();
					break;
				case ' '://space
					//do nothing as it is already handled
					break;
				default:
					if(this.autoCompPane.isVisible()){
						this.showAutoCompleteList();
					}
			}
        },
		
		onDisplayChanged: function(/*Event*/e){
            this.inherited('onDisplayChanged', arguments);
            if (this.isLoaded == true){
            	try{ this.checkCurrentLOCNode(); }catch(err){}
            }
		},
		
        /*
         * overriding functions - End
         */

        isLOC: function(node){
        	if (node != null && (node['nodeName'] != null && node.nodeName.toLowerCase() == 'div')){
				if (node.attributes != null && (node.attributes.getNamedItem('_loc') != null && node.attributes.getNamedItem('_loc').nodeValue == 'true')){
					return true;
				}
        	}
			return false;
        },
        
        getNumberOfLines: function(){
            return this.editNode.childNodes.length;
        },
        
        setMsgPane: function(pane){
        	this.msgPane = pane;
        	dojo.connect(this.msgPane, 'itemClicked', this, 'msgItemClickedListener');
        },
        
        msgItemClickedListener: function(){
        	this.focus();
        	this.editNode.childNodes[this.msgPane.selectedItem.errorObj.line].style.border = '1px red solid';
        },
        
        setAutoCompPane: function(pane){
        	this.autoCompPane = pane;
        	dojo.connect(this.autoCompPane, 'itemClicked', this, 'autoCompItemClickedListener');
        },
        
        autoCompItemClickedListener: function(){
        	this.insertSuggestion();
            this.hideAutoCompleteList();
        },
        
        outputHtml: function(html, clear){
        	if (clear != null && clear == true){
        		this.msgPane.clear();
        	}
			this.msgPane.writeLn(html);
        },
        
        output: function(text, clear){
			this.outputHtml(dijit._editor.escapeXml(text), clear);
        },
        
        getSelectedRange: function(){
        	var range = dijit.range.getSelection(this.window, false).getRangeAt(0);
        	if (range.collapsed == true){
        		if (range.endContainer.nodeValue == null || range.endContainer.nodeValue == ''){//cursor is at the beginning of the line
		            var tempRange = dijit.range.create();
		            tempRange.setStart(range.endContainer, 0);
		            tempRange.setEnd(range.endContainer, 0);
		        	return tempRange;
        		}
        	}
        	return range;
        },
        
        selectRange: function(startNode, startOffset, /* optional */ endNode, /* optional */ endOffset){
        	//remove old selection ranges
            var sel = dijit.range.getSelection(this.window, false);
            sel.removeAllRanges();

			//create new range
            var range = dijit.range.create();
            range.setStart(startNode, startOffset);
			if (endNode == null || endOffset == null){
	            range.setEnd(startNode, startOffset);
			} else {
	            range.setEnd(endNode, endOffset);
			}

			//add new range to selection			
            sel.addRange(range);
        },
        
        setSelectedRange: function(range){
        	this.selectRange(range.startContainer, range.startOffset, range.endContainer, range.endOffset);
        },
        
        getCurrentNode: function(){
            var range = this.getSelectedRange();
            return range.endContainer;
        },
        
        getCurrentNodeValue: function(){
        	var currentNode = this.getCurrentNode();
            return (currentNode != null && currentNode.nodeValue != null)?currentNode.nodeValue:'';
        },
        
        setCurrentNodeValue: function(value){
        	var currentNode = this.getCurrentNode();
            if (currentNode) {
                currentNode.nodeValue = value;
            }
        },
        
        getCurrentLOCNode: function(){
            var currNode = this.getCurrentNode();
            while(currNode != null && this.isLOC(currNode) != true){
            	currNode = currNode.parentNode;
            }
    		return currNode;
        },
        
        checkCurrentLOCNode: function(){
        	var range = this.getSelectedRange();
        	if (range.collapsed == false){
        		return;
        	}
        	var currNode = this.getCurrentNode();
            if (currNode != null && currNode.parentNode != null){
            	if (this.getCurrentLOCNode() == null && currNode.parentNode == this.editNode){
            		var currNodeValue = this.getCurrentNodeValue();
            		console.log(currNodeValue);
            		this.setCurrentNodeValue('');
            		var divLoc = this.insertNewLine();
            		if (currNodeValue != null && currNodeValue != ''){
	            		divLoc.innerHTML = currNodeValue;
						this.selectRange(divLoc, currNodeValue.length);
            		}
            	}
            }
        },
        
        extractAutocompleteStr: function(){
        	var currNode = this.getCurrentNode();
        	var nodeValue = this.getCurrentNodeValue();
        	var range = this.getSelectedRange();
        	var currentOffset = range.endOffset;
        	if (range.collapsed == false){
        		this.selectRange(currNode, currentOffset);
        	}
        	nodeValue = nodeValue.substring(0, currentOffset);
        	var lastIndexOfNonWordChar = nodeValue.lastIndexOfNonWordChar();
        	var strToComplete = nodeValue.substring((lastIndexOfNonWordChar + 1), currentOffset);
            return strToComplete;
        },
        
        insertHtml: function(str){
	    	this.execCommand("inserthtml", str);
        },
        
        insertStr: function(str, select){
			this.focus();
        	var range = this.getSelectedRange();
        	var rangeStart = range.startOffset;
        	var rangeEnd = range.endOffset;

        	var currNodeValue = this.getCurrentNodeValue();
        	var fstHalf = currNodeValue.substring(0, rangeStart);
        	var sndHalf = currNodeValue.substring(rangeEnd);
        	
        	var newNodeValue = fstHalf + str + sndHalf;
        	this.setCurrentNodeValue(newNodeValue);
			
			var currNode = this.getCurrentNode();
        	if (select != null && select == true){
	        	this.selectRange(currNode, rangeStart, currNode, rangeStart + str.length);
        	} else {
	        	this.selectRange(currNode, rangeStart + str.length);
        	}
        },
        
        insertNewLine: function(){
        	//TODO: if the cursor is in the middle of a statement
        	// move the second half of the statement starting from 
        	// the current position to the new line 
			var divLoc = dojo.clone(this._locTemplate);
			var currLOC = this.getCurrentLOCNode();
			if (currLOC == null){
				this.editNode.appendChild(divLoc);
			} else {
				if (currLOC.nextSibling == null){
					this.editNode.appendChild(divLoc);
				} else {
					currLOC.parentNode.insertBefore(divLoc, currLOC.nextSibling);
				}
			}
			this.selectRange(divLoc, 1);
			return divLoc;
        },
        
        showAutoCompleteList: function(){
			var strToComplete = this.extractAutocompleteStr();
			//console.log(strToComplete);
			if(strToComplete == null || strToComplete == '' || strToComplete.startsWith('.')){
				this.hideAutoCompleteList();
				return;
			}
			
	        if (this._lastStrToComplete == strToComplete){
				if(!this.autoCompPane.isVisible()){
					this.autoCompPane.show();
				}
	        	this.autoCompPane.moveSelection(1);
	        	return;
	        }
	        
	        //object members
			var objectAttributes = [];
			var objectFunctions = [];
            if (strToComplete == null || strToComplete == '') {
                objectAttributes = dojoide.core.listObjectAttributes(window, null);
                objectFunctions = dojoide.core.listObjectFunctions(window, null);
            } else {
                if (strToComplete.indexOf('.') < 0) {
                    objectAttributes = dojoide.core.listObjectAttributes(window, strToComplete);
                    objectFunctions = dojoide.core.listObjectFunctions(window, strToComplete);
                } else {
                    var beforeDotStr = strToComplete.substring(0, strToComplete.lastIndexOf('.'));
                    var afterDotStr = strToComplete.substring(strToComplete.lastIndexOf('.') + 1);
                    if(eval('window.' + beforeDotStr)){
	                    if (!afterDotStr || afterDotStr == '') {
	                        eval('objectAttributes = dojoide.core.listObjectAttributes(' + beforeDotStr + ', null)');
	                        eval('objectFunctions = dojoide.core.listObjectFunctions(' + beforeDotStr + ', null)');
	                    } else {
	                        eval('objectAttributes = dojoide.core.listObjectAttributes(' + beforeDotStr + ', \'' + afterDotStr + '\')');
	                        eval('objectFunctions = dojoide.core.listObjectFunctions(' + beforeDotStr + ', \'' + afterDotStr + '\')');
	                    }
                    }
                }
            }
	        this.autoCompPane.list(objectAttributes, true, 'Attributes');
	        this.autoCompPane.list(objectFunctions, false, 'Functions');
	        if (objectAttributes.length > 0 || objectFunctions.length > 0){
	            this.autoCompPane.show();
				var currLOCNode = this.getCurrentLOCNode();
	            this.autoCompPane.show((currLOCNode.offsetTop + currLOCNode.offsetHeight), currLOCNode.offsetLeft);
	        }
	        this._lastStrToComplete = strToComplete;
        },
        
        hideAutoCompleteList: function(){
	        this._lastStrToComplete = '';
	        this._suggestIndex = 0;
	        this.autoCompPane.clear();
	        this.autoCompPane.hide();
        },

        insertSuggestion: function(){
			var suggestion = this.autoCompPane.selectedItem;
        	if (!suggestion || suggestion == ''){
        		return;
        	}
        	
        	//extract the suggested part
			var strToComplete = this.extractAutocompleteStr();
        	var dotLastIndex = strToComplete.lastIndexOf('.');
        	if(dotLastIndex > -1){
	        	strToComplete = strToComplete.substring(dotLastIndex + 1);
        	}
        	var suggestedPart = suggestion.objName.substring(strToComplete.length);
			
			//insert the suggested part and don't highlight
			this.insertStr(suggestedPart, false);
        },
        
        getScript: function(){
//	        var findBreaks = new RegExp("<br(?: |).*?>", "g");
//        	var scriptArr = dojoide.core.unescapeXml(this.getValue()).split(findBreaks);
        	var scriptArr = [];
        	for (var i=0; i<this.editNode.childNodes.length; i++){
        		scriptArr.push(this.editNode.childNodes[i].innerHTML);
        	}

        	var scriptStr = '';
    		for (var i=0; i<scriptArr.length; i++){
    			if (scriptArr[i] != null && scriptArr[i] != ''){
	    			scriptArr[i] = dojoide.core.unescapeXml(dojoide.core.removeHtml(scriptArr[i]));
	    			scriptStr += scriptArr[i] + '\n';
    			}
    		}
			
			return {
				'scriptArr':scriptArr,
				'scriptStr':scriptStr
			};
        },

        validateScript: function(){
        	var editorScript = this.getScript();
			var myResult = JSLINT(editorScript.scriptStr);
	        
			if (myResult == false){//errors found
				var errorsArr = [];
				for(var i=0;i<JSLINT.errors.length;i++){
					var errorObj = JSLINT.errors[i];
					if (errorObj != null){
						errorsArr.push(new dojoide.workspace.CodeEditor.ErrorWrapper(errorObj));
					} else {
						errorsArr.push(new dojoide.workspace.CodeEditor.ErrorWrapper('Fatal error !'));
					}
				}
		        this.msgPane.list(errorsArr, true, null, null, true);
			} else {
				this.outputHtml('<div style="color: green;">No Errors found!</div>', true);
			}
        },
        
        runScript: function(){
        	var editorScript = this.getScript();
			var outputWindow = open('', 'OutputWindow');
			outputWindow.document.write('<html><head><script type="text/javascript" src="http://localhost:8080/dojo/1.1.0/dojo/dojo.js" djConfig="isDebug: true, parseOnLoad: true"></script></head><body><script type="text/javascript">' + editorScript.scriptStr + '</script></body></html>');
        }

        
    });
}
